package file

import (
	"crypto/md5"
	"encoding/hex"
	"fmt"
	"gitee.com/jokces/kit/errc"
	"gitee.com/jokces/kit/global/glconf"
	"github.com/aliyun/aliyun-oss-go-sdk/oss"
	"github.com/gin-gonic/gin"
	"mime/multipart"
	"os"
	"path"
	"strings"
)

type File struct {
	cfg glconf.FileConf //文件配置
	OSS *oss.Client
}

type SysFileRecord struct {
	FileName     string `json:"fileName"`     //文件名称
	FileOriginal string `json:"fileOriginal"` //原始文件名称
	FileTemp     string `json:"fileTemp"`     //原始文件地址
	Url          string `json:"url"`          //文件路径
	Hex          string `json:"hex"`          //文件的hash
	FileType     string `json:"fileType"`     //文件后缀
	FileSize     int64  `json:"fileSize"`     //文件大小
}

func NewFile(cfg glconf.FileConf) *File {
	f := &File{
		cfg: cfg,
	}
	if cfg.OssConf.Enable {
		client, err := oss.New(cfg.Endpoint, cfg.AccessKeyId, cfg.AccessKeySecret)
		if err != nil {
			panic(fmt.Sprintf("create OSS error: %v", err))
		}
		f.OSS = client
	}
	return f
}

func (f *File) OssCreateBuket(buket string) (*oss.Bucket, error) {
	err := f.OSS.CreateBucket(buket)
	if err != nil {
		return nil, err
	}
	bucket, err := f.OSS.Bucket(buket)
	if err != nil {
		return nil, err
	}
	return bucket, nil
}

func (f *File) OssExistBuket(buket string) (bool, error) {
	if exist, err := f.OSS.IsBucketExist(buket); err != nil {
		return false, err
	} else {
		return exist, nil
	}
}

func (f *File) OssCreateBuketWithCheck(buket string) (*oss.Bucket, error) {
	if f.cfg.Buket != "" {
		return &oss.Bucket{
			Client:     *f.OSS, //指针获取值对
			BucketName: f.cfg.Buket,
		}, nil
	}
	exist, err := f.OssExistBuket(buket)
	if err != nil {
		return nil, err
	}
	if exist {
		if bucket, err := f.OSS.Bucket(buket); err != nil {
			return nil, err
		} else {
			return bucket, nil
		}
	}
	return f.OssCreateBuket(buket)
}

// OssUpload 上传文件
// 最大5G的上传，上传的限制业务自行的控制来处理
func (f *File) OssUpload(file *multipart.FileHeader) (string, error) {
	filename := file.Filename
	fileFix := path.Ext(filename)
	path := f.cfg.OssConf.FilePath + "/" + md516(filename) + fileFix

	//打开文件
	src, err := file.Open()
	if err != nil {
		return "", err
	}
	defer src.Close()

	bk, err := f.OssCreateBuketWithCheck(f.cfg.Buket)
	if err != nil {
		return "", err
	}
	contentType := oss.TypeByExtension(fileFix)
	if err := bk.PutObject(path, src, oss.ContentType(contentType)); err != nil {
		return "", err
	}
	return f.cfg.ImageUrl + "/" + path, nil
}

func (f *File) OssUploadX(file *os.File) (string, error) {
	//MD5对文件名进行加密，然后取出16位，然后进行覆盖更新的处理
	filename := file.Name()
	fileFix := path.Ext(filename)
	path := f.cfg.OssConf.FilePath + "/" + md516(filename) + fileFix
	defer file.Close()

	bk, err := f.OssCreateBuketWithCheck(f.cfg.Buket)
	if err != nil {
		return "", err
	}
	contentType := oss.TypeByExtension(fileFix)
	if err := bk.PutObject(path, file, oss.ContentType(contentType)); err != nil {
		return "", err
	}
	return f.cfg.ImageUrl + "/" + path, nil
}

// LocalUpload
// 单个文件 上传文件到本地目录里
// 调用方法 utils.UploadFileToLocal(c)
// filepath 单个文件请采用 aaa/aaa/aaa 如此的目录方式处理
func (f *File) LocalUpload(c *gin.Context, filepath string) (SysFileRecord, error) {
	rs := SysFileRecord{}
	// 单个文件
	file, err := c.FormFile("file")
	if err != nil {
		return rs, errc.ErrParamInvalid.MultiMsg("need file")
	}
	rs.FileOriginal = file.Filename
	rs.FileSize = file.Size

	//可以控制其文件上传的大小

	//获取文件后缀
	suffix := ext(file.Filename)
	if suffix == "" || rs.FileSize <= 0 {
		return rs, errc.ErrParamInvalid.MultiMsg("file need suffix")
	}

	//如果没有filepath文件目录就创建一个
	newFilePath := f.cfg.LocalConf.FilePath
	if len(filepath) > 0 {
		newFilePath = newFilePath + "/" + filepath
	}

	//重新命名
	newFileSuffix := md516(rs.FileOriginal)
	file.Filename = fmt.Sprintf("%s.%s", newFileSuffix, suffix)
	path := newFilePath + "/" + file.Filename //路径+文件名上传

	// 上传文件到指定的目录
	err = c.SaveUploadedFile(file, path)
	if err != nil {
		return rs, errc.ErrParamInvalid.MultiMsg("file update error")
	}
	rs.FileName = file.Filename
	rs.Url = f.cfg.LocalConf.FileUrl + path
	rs.FileTemp = path
	rs.FileType = suffix
	return rs, nil
}

// 获取文件的扩展名
func ext(path string) string {
	for i := len(path) - 1; i >= 0 && path[i] != '/'; i-- {
		if path[i] == '.' {
			return path[i+1:]
		}
	}
	return ""
}

func md516(buffer string) string {
	h := md5.New()
	h.Write([]byte(buffer))
	password := hex.EncodeToString(h.Sum(nil))
	return strings.ToLower(password)[8:24]
}
